1 /*
2  * Hunt - a framework for web and console application based on Collie using Dlang development
3  *
4  * Copyright (C) 2015-2017  Shanghai Putao Technology Co., Ltd
5  *
6  * Developer: HuntLabs
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.http.response;
13 
14 import std.datetime;
15 import std.json;
16 
17 import collie.codec.http.headers.httpcommonheaders;
18 import collie.codec.http.server.responsehandler;
19 import collie.codec.http.server.responsebuilder;
20 import collie.codec.http.httpmessage;
21 import kiss.logger;
22 import hunt.http.cookie;
23 import hunt.utils.string;
24 import hunt.versions;
25 
26 enum XPoweredBy = "Hunt " ~ HUNT_VERSION;
27 
28 final class Response : ResponseBuilder
29 {
30     this(ResponseHandler resp)
31     {
32         super(resp);
33         setHttpStatusCode(200);
34     }
35 
36     auto setHeader(T = string)(string key, T value)
37     {
38         header!T(key,value);
39 
40         return this;
41     }
42 
43     auto setHeader(T = string)(HTTPHeaderCode key, T value)
44     {
45         header!T(key,value);
46 
47         return this;
48     }
49 
50     auto setContext(string str)
51     {
52         setBody(cast(ubyte[]) str);
53 
54         return this;
55     }
56 
57     auto setContext(ubyte[] data)
58     {
59         setBody(data);
60 
61         return this;
62     }
63 
64     ///set http status code eg. 404 200
65     auto setHttpStatusCode(ushort code)
66     {
67         status(code,HTTPMessage.statusText(code));
68 
69         return this;
70     }
71 
72     ///return json value
73     auto json(JSONValue js)
74     {
75         json(js.toString());
76 
77         return this;
78     }
79     ///render json string value
80     auto json(string jsonString)
81     {
82         setHeader(HTTPHeaderCode.CONTENT_TYPE, "application/json;charset=utf-8").setContext(jsonString);
83 
84         return this;
85     }
86 
87     ///render html string 
88     auto html(string htmlString, string content_type = "text/html;charset=utf-8")
89     {
90         setHeader(HTTPHeaderCode.CONTENT_TYPE, content_type).setContext(htmlString);
91 
92         return this;
93     }
94 
95     ///render plain text string 
96     auto plain(string textString, string content_type = "text/plain;charset=utf-8")
97     {
98         setHeader(HTTPHeaderCode.CONTENT_TYPE, content_type).setContext(textString);
99 
100         return this;
101     }
102 
103     ///download file 
104     auto download(string filename,ubyte[] file, string content_type = "binary/octet-stream")
105     {
106 		import std.conv;
107 		setHeader(HTTPHeaderCode.CONTENT_TYPE, content_type)
108 		.setHeader(HTTPHeaderCode.CONTENT_DISPOSITION,
109 				"attachment; filename="~filename~"; size="~(file.length.to!string))
110 		.setContext(file);
111 
112         return this;
113     }
114 
115     /**
116      * set Cookie
117      */
118     auto setCookie(string name, string value, int expires = 0, string path = "/", string domain = null)
119     {
120 
121         import std.typecons;
122         auto cookie = scoped!Cookie(name, value, [
123                 "path": path,
124                 "domain": domain
125             ]);
126 
127         if (expires != 0)
128         {
129             import std.conv; 
130             cookie.params["max-age"] = (expires < 0) ? "0" : to!string(expires);
131         }
132         
133         setHeader(HTTPHeaderCode.SET_COOKIE, cookie.output(""));
134 
135         return this;
136     }
137 
138 	/**
139 	 * delete Cookie
140 	 */
141 	auto delCookie(string name)
142 	{
143 		setCookie(name,null,0);
144 	}
145 
146     pragma(inline) final void done()
147     {
148         if(_isDone) return;
149 
150         _isDone = true;
151         setHeader(HTTPHeaderCode.X_POWERED_BY, XPoweredBy);
152         sendWithEOM();
153     }
154 
155     void redirect(string url, bool is301 = false)
156     {
157         if(_isDone) return;
158 
159         setHttpStatusCode((is301 ? 301 : 302));
160         setHeader(HTTPHeaderCode.LOCATION, url);
161 
162         connectionClose();
163         done();
164     }
165 
166     void do404(string body_ = "",string contentype = "text/html;charset=UTF-8")
167     {
168 		doError(404 , body_ , contentype);
169     }
170 
171 	void do403(string body_ = "",string contentype = "text/html;charset=UTF-8")
172 	{
173 		doError(403 , body_ , contentype);
174 	}
175 
176 	void doError(ushort code ,  string body_ = "",string contentype = "text/html;charset=UTF-8")
177 	{
178 		if(_isDone) return;
179 		
180 		setHttpStatusCode(code);
181 		header(HTTPHeaderCode.CONTENT_TYPE, contentype);
182 		setContext(errorPageHtml(code , body_));
183 		connectionClose();
184 		done();
185 	}
186 
187     void setHttpError(ushort code)
188     {
189         this.setHttpStatusCode(code);
190         this.setContext(errorPageHtml(code));
191     }
192 
193 package(hunt.http):
194     void clear()
195     {
196         setResponseHandler(null);
197     }
198 private:
199     bool _isDone = false;
200 }
201 
202 import hunt.http.code;
203 
204 string errorPageHtml(int code , string _body = "")
205 {
206     import std.conv : to;
207 
208     string text = code.to!string;
209     text ~= " ";
210     text ~= HTTPCodeText(code);
211 
212     string html = `<!doctype html>
213 <html lang="en">
214     <meta charset="utf-8">
215     <title>`;
216 
217     html ~= text;
218     html ~= `</title>
219     <meta name="viewport" content="width=device-width, initial-scale=1">
220     <style>
221 
222         * {
223             line-height: 3;
224             margin: 0;
225         }
226 
227         html {
228             color: #888;
229             display: table;
230             font-family: sans-serif;
231             height: 100%;
232             text-align: center;
233             width: 100%;
234         }
235 
236         body {
237             display: table-cell;
238             vertical-align: middle;
239             margin: 2em auto;
240         }
241 
242         h1 {
243             color: #555;
244             font-size: 2em;
245             font-weight: 400;
246         }
247 
248         p {
249             margin: 0 auto;
250             width: 90%;
251         }
252 
253     </style>
254 </head>
255 <body>
256     <h1>`;
257     html ~= text;
258     html ~= `</h1>
259     <p>Sorry!! Unable to complete your request :(</p>
260 	`;
261 	if(_body.length > 0)
262 		html ~= "<p>" ~ _body ~ "</p>";
263 html ~= `
264 </body>
265 </html>
266 `;
267 
268     return html;
269 }